Interactions

November 6, 2024

Interactions

  • Note that these lab notes rely on the same R script as the dummy variables lab notes.
library(haven)
library(tidyverse)
library(marginaleffects) 
library(jtools)

states <- read_dta("states.dta")
nes <- read_dta("nes.dta")

Interactions

# Supreme Court approval using feeling thermometer

# Don't forget to think about think about distribution of these DVs
ggplot(nes, aes(x=ft_SCOTUS)) +
  geom_histogram(color="white")

Interactions

  • Distributions of variables that are part of interaction.
# We're interested in party and knowledge; see how they're coded, which is key for 
# interpretations of interactions
nes |> group_by(partyid7) |> summarize(n=n()) |> mutate(pct=100*n/sum(n))
# A tibble: 8 × 3
  partyid7          n    pct
  <dbl+lbl>     <int>  <dbl>
1  1 [StrngDem]   890 20.8  
2  2 [WkDem]      560 13.1  
3  3 [IndDem]     490 11.5  
4  4 [Indep]      579 13.6  
5  5 [IndRep]     500 11.7  
6  6 [WkRep]      508 11.9  
7  7 [StrngRep]   721 16.9  
8 NA               23  0.539

Interactions

  • Distributions of variables that are part of interaction.
nes |> group_by(polknow) |> summarize(n=n()) |> mutate(pct=100*n/sum(n))
# A tibble: 7 × 3
  polknow     n   pct
    <dbl> <int> <dbl>
1       0   300  7.02
2       1   305  7.14
3       2   705 16.5 
4       3   844 19.8 
5       4   851 19.9 
6       5   644 15.1 
7      NA   622 14.6 

Model without Interaction

# Model without interactions - unconditional effects of party and knowledge
summary(lm(ft_SCOTUS ~ partyid7 + polknow + Female + as.factor(Race3), data=nes))

Call:
lm(formula = ft_SCOTUS ~ partyid7 + polknow + Female + as.factor(Race3), 
    data = nes)

Residuals:
    Min      1Q  Median      3Q     Max 
-61.759 -10.249   0.315  11.797  48.643 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)        56.1372     1.2239  45.866  < 2e-16 ***
partyid7           -0.5098     0.1650  -3.089 0.002024 ** 
polknow             1.0134     0.2375   4.267 2.04e-05 ***
Female              2.5948     0.6859   3.783 0.000158 ***
as.factor(Race3)2  -2.2314     1.2018  -1.857 0.063430 .  
as.factor(Race3)3   3.5580     1.1184   3.181 0.001480 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 19.27 on 3260 degrees of freedom
  (1005 observations deleted due to missingness)
Multiple R-squared:  0.01643,   Adjusted R-squared:  0.01492 
F-statistic: 10.89 on 5 and 3260 DF,  p-value: 2.072e-10

Model with Interaction

# Model with interactions - conditional effects of party and knowledge
# First, use "full data" that includes missing data (lm takes it out automatically)
h <- lm(ft_SCOTUS ~ partyid7 + polknow + partyid7*polknow + as.factor(Female) + 
          as.factor(Race3), data=nes)
summary(h, digits=3)

Call:
lm(formula = ft_SCOTUS ~ partyid7 + polknow + partyid7 * polknow + 
    as.factor(Female) + as.factor(Race3), data = nes)

Residuals:
    Min      1Q  Median      3Q     Max 
-61.862 -10.308   0.445  11.756  49.874 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)         58.9914     1.8320  32.200  < 2e-16 ***
partyid7            -1.2471     0.3890  -3.206 0.001358 ** 
polknow              0.1314     0.4837   0.272 0.785880    
as.factor(Female)1   2.5865     0.6855   3.773 0.000164 ***
as.factor(Race3)2   -2.6299     1.2161  -2.163 0.030649 *  
as.factor(Race3)3    3.4175     1.1199   3.052 0.002294 ** 
partyid7:polknow     0.2308     0.1103   2.093 0.036433 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 19.26 on 3259 degrees of freedom
  (1005 observations deleted due to missingness)
Multiple R-squared:  0.01775,   Adjusted R-squared:  0.01594 
F-statistic: 9.814 on 6 and 3259 DF,  p-value: 9.525e-11

Model with Interaction

# Alternative: If you include the multiplicative term, lm automatically
# includes the constituent terms. 
summary(lm(ft_SCOTUS ~ partyid7*polknow + as.factor(Female) + 
          as.factor(Race3), data=nes))

Call:
lm(formula = ft_SCOTUS ~ partyid7 * polknow + as.factor(Female) + 
    as.factor(Race3), data = nes)

Residuals:
    Min      1Q  Median      3Q     Max 
-61.862 -10.308   0.445  11.756  49.874 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)         58.9914     1.8320  32.200  < 2e-16 ***
partyid7            -1.2471     0.3890  -3.206 0.001358 ** 
polknow              0.1314     0.4837   0.272 0.785880    
as.factor(Female)1   2.5865     0.6855   3.773 0.000164 ***
as.factor(Race3)2   -2.6299     1.2161  -2.163 0.030649 *  
as.factor(Race3)3    3.4175     1.1199   3.052 0.002294 ** 
partyid7:polknow     0.2308     0.1103   2.093 0.036433 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 19.26 on 3259 degrees of freedom
  (1005 observations deleted due to missingness)
Multiple R-squared:  0.01775,   Adjusted R-squared:  0.01594 
F-statistic: 9.814 on 6 and 3259 DF,  p-value: 9.525e-11

Generate Conditional Marginal Effects

# Generate marginal effects of party id conditional on knowledge 
# We could do this manually -- write out equation

### Graph marginal effects (Brambor, Clark and Golder graph) 
# Use slopes command from marginaleffects package
mepid <- slopes(
  h, #model object
  variables = "partyid7", #variable for which we want marginal effect
  df = insight::get_df(h), #sets df for model to use t dist instead of z
  numderiv = "richardson", #SEs, delta method
  newdata = datagrid(polknow = 0:5)) #polknow the moderator, set range

Brambor, Clark, and Golder Graph

# Use ggplot, dotted lines for 95% CIs; add horizontal zero line
ggplot(mepid, aes(x = polknow)) + 
  geom_line(aes(y = estimate)) +
  geom_line(aes(y = conf.high), linetype = 2) +
  geom_line(aes(y = conf.low), linetype = 2) +
  geom_hline(yintercept = 0) +
  labs(title="Marginal effect of Party ID", x="Political knowledge", 
       y="Marginal effect of Party ID")

Brambor, Clark, and Golder Graph

# Use ribbon instead of dotted lines for 95% CI
ggplot(mepid, aes(x=polknow, y=estimate, ymin=conf.low, ymax=conf.high)) + 
  geom_line(color="black") +
  geom_ribbon(alpha=.3, fill="dodgerblue") +
  geom_hline(yintercept = 0, linetype="longdash", linewidth=.3) +
  labs(title="Marginal effect of Party ID", x="Political knowledge", 
       y="Marginal effect of Party ID")

Brambor, Clark, and Golder Graph

# Use same concept, but use dots and rspikes
ggplot(mepid, aes(x=polknow, y=estimate, ymin=conf.low, ymax=conf.high)) + 
  geom_point(color="black", size=2) +
  geom_errorbar(width=0) +
  theme(plot.title = element_text(face="bold", size=12, hjust = 0.5)) +
  geom_hline(yintercept = 0, linetype="dashed") +
  theme_minimal() +
  labs(title="Marginal Effect of Party ID", x="Political knowledge", 
       y="Marginal effect of Party ID") +
  theme(plot.title = element_text(face="bold", size=12, hjust = 0.5))

Brambor, Clark, and Golder Graph

# Flip to horizontal; typically used when you label the vertical axes with long labels
ggplot(mepid, aes(x=polknow, y=estimate, ymin=conf.low, ymax=conf.high)) + 
  geom_point(color="black", size=2) +
  geom_errorbar(width=0) +
  geom_hline(yintercept = 0, linetype="dashed") +
  theme_minimal() +
  coord_flip() +
  scale_x_continuous(breaks=c(0:5), 
                     labels=c('Very low knowledge','Low knowledge','Med-low knowledge',
                              'Med-high knowledge','High knowledge','Very high knowledge')) +
  labs(title="Marginal Effect of Party ID", x=NULL, 
       y="Marginal effect of Party ID") +
  theme(plot.title = element_text(face="bold", size=12, hjust = 0.5))

Graphing Method 2

## Second way of graphing interactions: 
# Plot partial slopes for the effect of party on SC evals conditional on 
# low and high knowledge.

lknow <- predictions(
  h,
  by="partyid7",
  df = insight::get_df(h),
  numderiv = "richardson",
  newdata = datagrid(
    polknow = 0, partyid7=1:7, grid_type="counterfactual"))

hknow <- predictions(
  h,
  by="partyid7",
  numderiv = "richardson",
  df = insight::get_df(h),
  newdata = datagrid(
    polknow = 5, partyid7=1:7, grid_type="counterfactual"))

Graphing Method 2

ggplot(data=lknow, aes(x=partyid7, y=estimate)) + 
  geom_line() +
  geom_line(data=hknow, aes(x=partyid7, y=estimate), color="dodgerblue") +
  geom_text(x=4, y=62, label="High pol. knowledge", color="dodgerblue") + 
  geom_text(x=5, y=53, label="Low pol. knowledge", color="black") +
  scale_x_continuous(breaks=c(1:7), labels=c('SD','WD','ID','I','IR','WR','SR')) +
  scale_y_continuous(limits = c(50, 65), breaks = seq(50, 65, by = 5)) + 
  theme_minimal() +
  labs(title="Conditional Effect of Party ID", x="Party ID",  
       y="Supreme Court Thermometer") +
  theme(plot.title = element_text(face="bold", size=12, hjust = 0.5))

Graphing Method 2